package com.kang.c0c.util;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.text.Document;

import com.kang.c0c.optimize.GlobalRegAssign;
import com.kang.c0c.optimize.Reg;
import com.kang.c0c.parser.Parser;
import com.kang.c0c.symbol.SymbolScanner;
import com.kang.c0c.tree.TreeDivSimpleBlock;
import com.kang.c0c.tree.TreeScanner;
import com.kang.c0c.tree.TreeTranslator;

public class MainFrame extends JFrame implements DocumentListener{
	private static final long serialVersionUID = 1L;

	//source file you want to compile
	private File sourceFile = null;
	
	//file to store the result generated by compiler
	private String defaultFilePath = "src/com/kang/c0c/out/";
	
	//the following variables are used in the GUI
	private JPanel jContentPane = null;
	private JToolBar toolBar = null;
	private JMenuBar menuBar = null;
	private JMenu fileMenu = null;
	private JMenu compileMenu = null;
	private JMenu aboutMenu = null;
	private Action openAction = null;
	private Action saveAction = null;
	private Action newAction = null;
	private Action aboutAction = null;
	private Action compileAction = null;
	private Action midCodeAction = null;
	private Action exitAction = null;
	private JScrollPane inScrollPane = null;
	private JTextArea inTextArea = null;
	private JScrollPane outScrollPane = null;
	private JTextArea outTextArea = null;
	private JSplitPane splitPane = null;

	private Document indocument;
	private boolean edited = false;
	
	public MainFrame(){
		super("C0 Complier Designed By KANG");
		initialize();
	}
	
	/*
	 * 画窗口
	 */
	private void initialize() {
		this.setSize(1080, 740);
		this.setContentPane(getJContentPane());
		this.setJMenuBar(getmenuBar());
		this.setBounds(200, 150, 1080, 740);
		this.setLocation(new Point(10,10));
		this.setVisible(true);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

	}
	
	private JPanel getJContentPane() {
		if (jContentPane == null) {
			jContentPane = new JPanel();
			jContentPane.setLayout(new BorderLayout());
			jContentPane.add(getToolBar(), java.awt.BorderLayout.NORTH);
			jContentPane.add(getSplitPane(), java.awt.BorderLayout.CENTER);

		}
		return jContentPane;
	}
	
	private JMenuBar getmenuBar() {
		if (menuBar == null) {
			menuBar = new JMenuBar();
			fileMenu = new JMenu("File");
			fileMenu.setMnemonic('F');
			fileMenu.add(getNewAction());
			fileMenu.add(getOpenAction());
			fileMenu.add(getSaveAction());
			fileMenu.addSeparator();
			fileMenu.add(getExitAction());
			menuBar.add(fileMenu);
			
			compileMenu = new JMenu("Run");
			compileMenu.setMnemonic('R');			
			compileMenu.add(getMidCodeAction());
			compileMenu.add(getCompileAction());
			menuBar.add(compileMenu);
			
			aboutMenu = new JMenu("About");
			aboutMenu.setMnemonic('A');
			aboutMenu.add(getAboutAction());
			menuBar.add(aboutMenu);

		}
		return menuBar;
	}
	
	private JToolBar getToolBar() {
		if (toolBar == null) {
			toolBar = new JToolBar();
			toolBar.add(getOpenAction());
			toolBar.add(getSaveAction());
			toolBar.add(getNewAction());
			toolBar.add(getMidCodeAction());
			toolBar.add(getCompileAction());
			toolBar.add(getAboutAction());
			toolBar.add(getExitAction());
		}
		return toolBar;
	}

	private JSplitPane getSplitPane() {
		if (splitPane == null) {
			splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
					getInScrollPane(), getOutScrollPane());
			splitPane.setOneTouchExpandable(true);
			splitPane.setDividerLocation(540);

		}
		return splitPane;
	}
	
	private JScrollPane getInScrollPane() {
		if (inScrollPane == null) {
			inScrollPane = new JScrollPane();
			inScrollPane.setViewportView(getInTextArea());

		}
		return inScrollPane;
	}
	
	private JTextArea getInTextArea() {
		if (inTextArea == null) {
			inTextArea = new JTextArea();
			indocument = inTextArea.getDocument();
			indocument.addDocumentListener(this);
			inTextArea.setEditable(false);
			inTextArea.setForeground(Color.BLUE);
			inTextArea.setFont(new Font(null, Font.BOLD, 14));
		}
		return inTextArea;
	}
	
	private JScrollPane getOutScrollPane() {
		if (outScrollPane == null) {
			outScrollPane = new JScrollPane();
			outScrollPane.setViewportView(getOutTextArea());
		}
		return outScrollPane;
	}
	
	private JTextArea getOutTextArea() {
		if (outTextArea == null) {
			outTextArea = new JTextArea();
			outTextArea.setEditable(false);
			outTextArea.setForeground(Color.BLUE);
			outTextArea.setFont(new Font(null, Font.BOLD, 12));
		}
		return outTextArea;
	}
	
	/*
	 * 添加action
	 */

	private Action getOpenAction() {
		if (openAction == null) {
			openAction = new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					openFile();
				}
			};
			openAction.putValue(Action.NAME, "Open");
			openAction.putValue(Action.SMALL_ICON, new ImageIcon(
					MainFrame.class.getResource("/com/kang/c0c/util/img/open.png")));
			openAction.putValue(Action.SHORT_DESCRIPTION, "Open a file");
			openAction.putValue(Action.MNEMONIC_KEY, new Integer('O'));
		}
		return openAction;
	}
	
	private Action getSaveAction() {
		if (saveAction == null) {
			saveAction = new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					saveFile();
				}
			};
			saveAction.putValue(Action.NAME, "Save");
			saveAction.putValue(Action.SMALL_ICON, new ImageIcon(
					MainFrame.class.getResource("/com/kang/c0c/util/img/save.png")));
			saveAction.putValue(Action.SHORT_DESCRIPTION, "Save the current file");
			saveAction.putValue(Action.MNEMONIC_KEY, new Integer('S'));
		}
		return saveAction;
	}
	
	private Action getNewAction() {
		if (newAction == null) {
			newAction = new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					newFile();
				}
			};
			newAction.putValue(Action.NAME, "New");
			newAction.putValue(Action.SMALL_ICON, new ImageIcon(
					MainFrame.class.getResource("/com/kang/c0c/util/img/new.png")));
			newAction.putValue(Action.SHORT_DESCRIPTION, "Create a new file");
			newAction.putValue(Action.MNEMONIC_KEY, new Integer('N'));
		}
		return newAction;
	}
	
	private Action getCompileAction() {
		if (compileAction == null) {
			compileAction = new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					MainRun();
				}
			};
			compileAction.putValue(Action.NAME, "Compile");
			compileAction.putValue(Action.SMALL_ICON, new ImageIcon(
					MainFrame.class.getResource("/com/kang/c0c/util/img/run.png")));
			compileAction.putValue(Action.SHORT_DESCRIPTION,
					"Compile");
			compileAction.putValue(Action.MNEMONIC_KEY, new Integer('C'));
		}
		return compileAction;
	}
	
	private Action getMidCodeAction() {
		if (midCodeAction == null) {
			midCodeAction = new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					Build();
				}
			};
			midCodeAction.putValue(Action.NAME, "MidCode");
			midCodeAction.putValue(Action.SMALL_ICON, new ImageIcon(
					MainFrame.class.getResource("/com/kang/c0c/util/img/build.png")));
			midCodeAction.putValue(Action.SHORT_DESCRIPTION,
					"MidCode");
			midCodeAction.putValue(Action.MNEMONIC_KEY, new Integer('M'));
		}
		return midCodeAction;
	}
	
	private Action getAboutAction() {
		if (aboutAction == null) {
			aboutAction = new AbstractAction() {
				Icon imageme = new ImageIcon(
						MainFrame.class.getResource("/com/kang/c0c/util/img/about.png"));

				public void actionPerformed(ActionEvent e) {
					JOptionPane.showMessageDialog(null,
							"项目：C0文法编译器\n" +
							"学号：36060417\n" +
							"作者：陈文涛\n" +
							"小组：KANG TEAM\n" +							
							"联系：kangcwt@gmail.com",
							"关于作者",
							JOptionPane.INFORMATION_MESSAGE, imageme);
				}
			};
			aboutAction.putValue(Action.NAME, "About");
			aboutAction.putValue(Action.SMALL_ICON, new ImageIcon(
					MainFrame.class.getResource("/com/kang/c0c/util/img/about.png")));
			aboutAction
					.putValue(Action.SHORT_DESCRIPTION, "About the author");
			aboutAction.putValue(Action.MNEMONIC_KEY, new Integer('A'));
		}
		return aboutAction;
	}
	
	private Action getExitAction() {
		if (exitAction == null) {
			exitAction = new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					System.exit(0);
				}
			};
			exitAction.putValue(Action.NAME, "Exit");
			exitAction.putValue(Action.SMALL_ICON, new ImageIcon(
					MainFrame.class.getResource("/com/kang/c0c/util/img/exit.png")));
			exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit");
			exitAction.putValue(Action.MNEMONIC_KEY, new Integer('E'));
		}
		return exitAction;
	}
	
	/*
	 * 文件操作
	 */
	
	private void openFile() {
		JFileChooser fc = new JFileChooser();
		FileNameExtensionFilter filter = new FileNameExtensionFilter(
		        "txt","doc","asm","c");
		fc.setFileFilter(filter);
		
		int returnVal = fc.showOpenDialog(this);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			sourceFile = fc.getSelectedFile();
			try {
				inTextArea.read(new FileReader(sourceFile), null);
				this.setTitle("C0 Compiler - " + sourceFile.getName());
				inTextArea.setEditable(true);
				edited = false;
				indocument = inTextArea.getDocument();
				indocument.addDocumentListener(this);
				outTextArea.setText("");
			} catch (IOException ioe) {
				JOptionPane.showMessageDialog(this, "Can not open this file!",
						"Error", JOptionPane.ERROR_MESSAGE);
			}
		}
	}
	
	private void saveFile() {
		if (!inTextArea.isEditable()) {
			JOptionPane.showMessageDialog(this, "Please create a new file",
					"Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
		if (sourceFile == null) {
			JFileChooser fc = new JFileChooser();
			int returnVal = fc.showSaveDialog(this);
			if (returnVal == JFileChooser.APPROVE_OPTION) {
				sourceFile = fc.getSelectedFile();
			} else
				return;
		}
		try {
			inTextArea.write(new FileWriter(sourceFile));
			this.setTitle("C0 Compiler - " + sourceFile.getName());
			edited = false;

		} catch (IOException ioe) {
			JOptionPane.showMessageDialog(this, "can not open this file!",
					"Error", JOptionPane.ERROR_MESSAGE);
		}

	}
	
	private void newFile() {
		sourceFile = null;
		inTextArea.setText("");
		this.setTitle("C0 Compiler - Noname");
		inTextArea.setEditable(true);
		outTextArea.setText("");
		indocument = inTextArea.getDocument();
		indocument.addDocumentListener(this);
		edited = false;

	}
	
	
	/*
	 * DocumentListener to handle the changes of inTextArea when the current
	 * content of inTextArea, it will show '*' on the title to inform the user
	 * of the changing
	 * 
	 * @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent)
	 */
	public void changedUpdate(DocumentEvent e) {
	}

	public void insertUpdate(DocumentEvent e) {
		if (!edited) {
			edited = true;
			setTitle(getTitle() + " *");
		}
	}

	public void removeUpdate(DocumentEvent e) {
		if (!edited) {
			edited = true;
			setTitle(getTitle() + " *");
		}
	}

	private String readFile(String fileName) throws IOException{
		FileInputStream input = new FileInputStream(fileName);
		InputStreamReader reader = new InputStreamReader(input);
		int length = input.available() + 1;
		char[] buf = new char[length];
		
		reader.read(buf,0,length);
		reader.close();
		buf[buf.length-1] = '\0';
		
		String str = new String(buf);
		
		return str;
		
	}
	
	private void MainRun(){
		if(sourceFile == null){
			JOptionPane.showMessageDialog(this, "Please choose a file to compile!",
					"Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		outTextArea.setText("");
		outTextArea.append("Compiling...\n");
		outTextArea.append(sourceFile.getAbsolutePath() + "\n");
		outTextArea.append("\n\n\n");
		
		Context context = new Context(sourceFile);
		Parser p = new Parser(context);
		
		try{
			p.Program();
		}catch(Exception ex){
			outTextArea.append(ex.getMessage() + "\n");
		}finally{
			try{
				Log log = Log.instance(context);
				if(log.getNerrors() == 0){
					TreeDivSimpleBlock opt = new TreeDivSimpleBlock(context);
					opt.divBlock();
				}else{
					log.analyzeReport();
					outTextArea.append(readFile(defaultFilePath + "error.txt"));
					outTextArea.append("\n\n\n");
					context.clear();
					return;
				}
				
				log.analyzeReport();
				outTextArea.append(readFile(defaultFilePath + "error.txt"));
				outTextArea.append("\n\n\n");
				if(log.getNerrors() != 0){
					log.analyzeReport();
					return;
				}
				
				GlobalRegAssign assign = new GlobalRegAssign(context);
				assign.RegAssign();
				TreeTranslator translator = new TreeTranslator(context,"out.asm");
				translator.translateTree(context);
				outTextArea.append(readFile(defaultFilePath + "out.asm"));
			}catch(IOException ex){
				outTextArea.append(ex.getMessage() + "\n");
			}
		}
		context.clear();
		
		int index = 0;
		while(index < 7){
			Reg reg = Reg.regs[index];
			reg.clear();
			index ++;
		}
	}
	
	private void Build(){
		if(sourceFile == null){
			JOptionPane.showMessageDialog(this, "Please choose a file to compile!",
					"Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		outTextArea.setText("");
		outTextArea.append("Compiling...\n");
		outTextArea.append(sourceFile.getAbsolutePath() + "\n");
		outTextArea.append("\n\n\n");
		
		Context context = new Context(sourceFile);
		Parser p = new Parser(context);
		
		try{
			p.Program();
		}catch(Exception ex){
			outTextArea.append(ex.getMessage() + "\n");
		}finally{
			try{
				Log log = Log.instance(context);
				if(log.getNerrors() == 0){
					TreeDivSimpleBlock opt = new TreeDivSimpleBlock(context);
					opt.divBlock();
				}else{
					log.analyzeReport();
					outTextArea.append(readFile(defaultFilePath + "error.txt"));
					outTextArea.append("\n\n\n");
					
					context.clear();
					return;
				}
				
				log.analyzeReport();
				outTextArea.append(readFile(defaultFilePath + "error.txt"));
				outTextArea.append("\n\n\n");
				if(log.getNerrors() != 0){
					log.analyzeReport();
					return;
				}
				
				SymbolScanner scanner = new SymbolScanner(context, "symbol.txt");
				scanner.showSymbolTable();
				outTextArea.append(readFile(defaultFilePath + "symbol.txt"));
				outTextArea.append("\n\n\n");
				
				TreeScanner midCode = new TreeScanner(context, "middleCode.txt");
				midCode.showTree();
				outTextArea.append(readFile(defaultFilePath + "middleCode.txt"));
				
			}catch(IOException ex){
				outTextArea.append(ex.getMessage() + "\n");
			}
		}
		
		context.clear();
	}
}
